package com.panopset.io;

import static com.panopset.Util.*;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

/**
 * JarResources: JarResources maps all resources included in a
 * Zip or Jar file.
 *
 * @author <ul><li>http://www.javaworld.com/javaworld/javatips/javatip49/JarResources.java: Original source.</li><li>
 * Karl Dinwiddie: Java 5 compatibility, sort by keys, other changes for flexibility.
 * </li></ul>

 *
 */
public final class JarResources {

   private Map<String, Long> sizes;
   private Map<String, byte[]> jarContents;

   private String jarFileName;
   private File jarFile;

   /**
    * @param jarFileName a jar or zip file path
    */
   public JarResources(String jarFileName) {
      this.jarFileName=jarFileName;
   }

   /**
    * @param jarFile a jar or zip file
    */
   public JarResources(File jarFile) {
       this.jarFile = jarFile;
   }

   private File getJarFile() {
       if (jarFile == null) {
           jarFile = new File(jarFileName);
       }
       return jarFile;
   }

   /**
    * Get contents of the jar
    * @return Hashtable<String, byte[]> where the key is the resource name.
    * @throws IOException
    */
   public Map<String, byte[]> getJarContents() {
        if (jarContents == null) {
            jarContents = Collections.synchronizedSortedMap(new TreeMap<String, byte[]>());
            FileInputStream fis = null;
            BufferedInputStream bis = null;
            ZipInputStream zis = null;

            try {
                fis = new FileInputStream(getJarFile());
                bis = new BufferedInputStream(fis);
                zis = new ZipInputStream(bis);
                ZipEntry ze = null;
                while ((ze = zis.getNextEntry()) != null) {
                    if (ze.isDirectory()) {
                        continue;
                    }
                    if (verbose) {
                        log("ze.getName()=" + ze.getName() + "," + "getSize()="
                                + ze.getSize());
                    }
                    long size = ze.getSize();
                    // -1 means unknown size.
                    if (size == -1) {
                        size = getSizes().get(ze.getName()).longValue();
                    }
                    byte[] b = new byte[(int) size];
                    int rb = 0;
                    int chunk = 0;
                    while (((int) size - rb) > 0) {
                        chunk = zis.read(b, rb, (int) size - rb);
                        if (chunk == -1) {
                            break;
                        }
                        rb += chunk;
                    }
                    // add to internal resource hashtable
                    jarContents.put(ze.getName(), b);
                    if (verbose) {
                        log(ze.getName() + "  rb=" + rb + ",size=" + size
                                + ",csize=" + ze.getCompressedSize());
                    }
                }
            } catch (IOException e) {
                log(e);
            } finally {
                if (zis != null) {
                    try {
                        zis.close();
                    } catch (IOException e) {
                        log(e);
                    }
                }
            }

        }
        return jarContents;
   }

   /**
    * Get jar resource sizes
    * @return Hashtable<String, Integer> where the key is the resource name.
    * @throws IOException
    */
   public Map<String, Long> getSizes() throws IOException {
        if (sizes == null) {
            sizes = Collections.synchronizedSortedMap(new TreeMap<String, Long>());

            ZipFile zf = new ZipFile(getJarFile());
            Enumeration<? extends ZipEntry> e = zf.entries();
            while (e.hasMoreElements()) {
                ZipEntry ze = e.nextElement();
                if (verbose) {
                    log(dumpZipEntry(ze));
                }
                sizes.put(ze.getName(), ze.getSize());
            }
            zf.close();
        }
        return sizes;
    }

   /**
    * Extracts a jar resource as a blob.
    * @param name a resource name.
    */
   public byte[] getResource(String name) throws IOException {
      return getJarContents().get(name);
   }

   /**
    * Dumps a zip entry into a string.
    * @param ze a ZipEntry
    */
   private String dumpZipEntry(ZipEntry ze) {
       StringBuffer sb=new StringBuffer();
       if (ze.isDirectory()) {
          sb.append("d ");
       } else {
          sb.append("f ");
       }
       if (ze.getMethod()==ZipEntry.STORED) {
          sb.append("stored   ");
       } else {
          sb.append("defalted ");
       }
       sb.append(ze.getName());
       sb.append("\t");
       sb.append(""+ze.getSize());
       if (ze.getMethod()==ZipEntry.DEFLATED) {
          sb.append("/"+ze.getCompressedSize());
       }
       return (sb.toString());
   }

   /**
    * Is a test driver. Given a jar file and a resource name, it trys to
    * extract the resource and then tells us whether it could or not.
    *
    * <strong>Example</strong>
    * Let's say you have a JAR file which jarred up a bunch of gif image
    * files. Now, by using JarResources, you could extract, create, and display
    * those images on-the-fly.
    * <pre>
    *     ...
    *     JarResources JR=new JarResources("GifBundle.jar");
    *     Image image=Toolkit.createImage(JR.getResource("logo.gif");
    *     Image logo=Toolkit.getDefaultToolkit().createImage(
    *                   JR.getResources("logo.gif")
    *                   );
    *     ...
    * </pre>
    */
   public static void main(String[] args) throws IOException {
       if (args.length!=2) {
          System.err.println(
             "usage: java JarResources <jar file name> <resource name>"
             );
          System.exit(1);
       }
       JarResources jr=new JarResources(args[0]);
       byte[] buff=jr.getResource(args[1]);
       if (buff==null) {
          System.out.println("Could not find "+args[1]+".");
       } else {
          System.out.println("Found "+args[1]+ " (length="+buff.length+").");
       }
   }

}    // End of JarResources class.
